import * as Redux from 'redux';

import {
    Action,
} from '../action';

const PENDING = 'PENDING';
const START = 'START';
const FULFILLED = 'FULFILLED';
const SUCCESS = 'SUCCESS';
const REJECTED = 'REJECTED';

export function getFetchActionType(type: string, status: string): string {
    return `[${type}]_${status}`;
}

export interface FetchInfo {
    canFetch: boolean;
    pending: boolean;
    reason: string;
    serverError: string;
}

export interface FetchPagingInfo extends FetchInfo {
    page: number;
    nextPage: boolean;
    isEmpty: boolean;
}

const ONE_PAGE_COUNT = 10;

const headers: Headers = new Headers();
headers.set('Content-Type', 'application/json');

let generateUUID: () => any = () => Date.now();
export function setGenerateUUID(fun: () => any): void {
    generateUUID = fun;
}

const payloadKeyFunArray: { key: string, fun: (store: any) => any }[] = [];
export function setPayloadKeyFun(key: string, fun: (store: any) => any): void {
    const index = payloadKeyFunArray.findIndex(v => v.key == key);
    if (index >= 0) {
        payloadKeyFunArray.splice(index, 1);
    }
    payloadKeyFunArray.push({ key, fun });
}

const postKeyFunArray: { key: string, fun: (store: any) => any }[] = [];
export function setPostKeyFun(key: string, fun: (store: any) => any): void {
    const index = postKeyFunArray.findIndex(v => v.key == key);
    if (index >= 0) {
        postKeyFunArray.splice(index, 1);
    }
    postKeyFunArray.push({ key, fun });
}

export function createFetchPromiseMiddleware() {
    return (res: { dispatch: Redux.Dispatch<any>, getState: () => any }) => (next: (action: any) => any) => (action: any) => {
        const { type, payload } = action as Action;
        let meta = action.meta ? action.meta : {};
        if (!needFetch(action) || !payload) {
            return next(action);
        }
        const UUID = action.UUID ? action.UUID : generateUUID();
        const otherPayload: any = {};
        payloadKeyFunArray.forEach(v => {
            otherPayload[v.key] = v.fun(res.getState());
        });
        if (!otherPayload.url) {
            return next(action);
        }
        let page = 0;
        const getAction = (newPayload: any, result: any, status: string): Action => ({
            ignoreFetchPromise: true,
            type: getFetchActionType(type, status),
            payload: { ...payload, ...newPayload, ...otherPayload, },
            meta: { ...meta, ...(page > 0 ? { page } : {}) },
            UUID,
            result,
        });
        const pendingAction = getAction({}, null, PENDING);
        next(pendingAction);
        let userCancel = false;
        if (pendingAction.payload && pendingAction.payload.cancelFetch) {
            userCancel = pendingAction.payload.cancelFetch;
        }
        const info = getFetchInfo(payload.fetchInfoKey ? payload.fetchInfoKey : payload.fetch, res.getState());
        let promise = null;
        if (info.canFetch && !userCancel) {
            const otherP: any = {};
            postKeyFunArray.forEach(v => {
                otherP[v.key] = v.fun(res.getState());
            });
            let body = '';
            if (payload.paging) {
                page = (info as FetchPagingInfo).page;
                body = generatePostData({ ...meta, ...otherP, page });
            }
            else {
                body = generatePostData({ ...meta, ...otherP });
            }
            next(getAction({ body }, null, START));
            promise = new Promise((resolve, reject) => {
                fetch(otherPayload.url + payload.fetch
                    , {
                        method: 'POST',
                        body,
                        headers,
                    }
                )
                    .then((response) => {
                        if (response.status == 200) {
                            response.json()
                                .then((data) => {
                                    resolve(data);
                                })
                                .catch((reason) => {
                                    reject({ reason, flag: true, html: response });
                                });
                        }
                        else {
                            response.text()
                                .then((text) => {
                                    reject({ reason: response.status, flag: true, html: text });
                                }).catch(() => {
                                    reject({ reason: response.status, flag: true, html: response });
                                });
                        }
                    })
                    .catch((reason) => {
                        reject({ reason, flag: true });
                    });
            });
        }
        else {
            let reason = info.reason;
            if (userCancel && pendingAction.payload) {
                reason = pendingAction.payload.reason ? pendingAction.payload.reason : '在PENDING阶段手动取消';
            }
            promise = new Promise((resolve, reject) => {
                reject({ reason, flag: false, cancelFetch: userCancel });
            });
        }
        const promiseValue = promise
            .then((data) => {
                const { no, d, m } = data as any;
                if (no == 200) {
                    return res.dispatch(getAction({}, { no, data: d }, SUCCESS));
                }
                else {
                    return res.dispatch(getAction({}, { no, err: m }, FULFILLED));
                }
            })
            .catch((reason) => {
                return res.dispatch(getAction({ reason: reason.reason, resetPending: reason.flag, cancelFetch: reason.cancelFetch, html: reason.html }, null, REJECTED));
            });
        return promiseValue;
    };
}

function needFetch(action: Action): boolean {
    if (action.payload && (!action.ignoreFetchPromise)) {
        if (action.payload.fetch) {
            return true;
        }
    }
    return false;
}

function generatePostData(value: any): string {
    // const keys = Object.keys(value);
    // return keys.map((key) => {
    //     let tmp = value[key];
    //     return key + '=' + tmp;
    // }).join('&');
    return JSON.stringify(value);
}

function getLength(value: any): number {
    if (null == value) {
        return 0;
    } else if (Array.isArray(value)) {
        return value.length;
    }
    else if (typeof value === 'object') {
        return Object.keys(value).length;
    }
    return 0;
}

const FETCH_ACTIONS_HANDLER = {
    [PENDING]: (state: any, action: Action) => {
        if (!action.payload) {
            return state;
        }
        const key = action.payload.fetchInfoKey ? action.payload.fetchInfoKey : action.payload.fetch;
        const info = state[key] ? Object.assign({}, state[key]) : action.payload.paging
            ? { canFetch: false, pending: false, page: 1, nextPage: true, isEmpty: false }
            : { canFetch: false, pending: false };
        if (action.payload.mandatory) {
            info.pending = false;
            if (action.payload.paging) {
                info.page = 1;
                info.nextPage = true;
                info.isEmpty = false;
                if (action.meta && action.meta.page) {
                    info.page = action.meta.page;
                }
            }
        }
        if (info.pending) {
            info.canFetch = false;
            info.reason = '上一个请求未完成';
        }
        else if (action.payload.paging && !info.nextPage) {
            info.canFetch = false;
            info.reason = '列表页数已经到最大了';
        }
        else {
            info.canFetch = true;
        }
        return { ...state, [key]: info };
    },
    [START]: (state: any, action: Action) => {
        if (action.payload) {
            const key = action.payload.fetchInfoKey ? action.payload.fetchInfoKey : action.payload.fetch;
            const info = state[key] ? Object.assign({}, state[key]) : null;
            if (info) {
                info.pending = true;
                return { ...state, [key]: info };
            }
        }
        return state;
    },
    [REJECTED]: (state: any, action: Action) => {
        if (action.payload) {
            if (action.payload.resetPending) {
                const key = action.payload.fetchInfoKey ? action.payload.fetchInfoKey : action.payload.fetch;
                const info: FetchInfo = state[key] ? Object.assign({}, state[key]) : null;
                if (info) {
                    info.pending = false;
                    info.serverError = action.payload.reason ? action.payload.reason : '';
                    return { ...state, [key]: info };
                }
            }
        }
        return state;
    },
    [FULFILLED]: (state: any, action: Action) => {
        if (action.payload) {
            const key = action.payload.fetchInfoKey ? action.payload.fetchInfoKey : action.payload.fetch;
            const info: FetchInfo = state[key] ? Object.assign({}, state[key]) : null;
            if (info) {
                info.pending = false;
                if (action.result) {
                    info.serverError = action.result.no + ':' + action.result.err;
                }
                return { ...state, [key]: info };
            }
        }
        return state;
    },
    [SUCCESS]: (state: any, action: Action) => {
        if (action.payload) {
            const key = action.payload.fetchInfoKey ? action.payload.fetchInfoKey : action.payload.fetch;
            const info: FetchInfo = state[key] ? Object.assign({}, state[key]) : null;
            if (info) {
                info.pending = false;
                if (action.result && action.result.data) {
                    const { data } = action.result;
                    if (action.payload.paging) {
                        const pageInfo: FetchPagingInfo = (info as FetchPagingInfo);
                        const len = action.payload.pageListData ? getLength(data[action.payload.pageListData]) : getLength(data);
                        pageInfo.nextPage = len >= ONE_PAGE_COUNT;
                        if (pageInfo.page == 1) {
                            pageInfo.isEmpty = len == 0;
                        }
                        pageInfo.page++;
                    }
                }

                return { ...state, [key]: info };
            }
        }
        return state;
    },
};

export function fetchReducer(state: any = {}, action: Action) {
    let handler = null;
    if (action.payload) {
        if (action.payload.fetch) {
            if (action.type[0] == '[' && (action.type.indexOf(']_' + PENDING) > 0)) {
                handler = FETCH_ACTIONS_HANDLER[PENDING];
            }
            else if (action.type[0] == '[' && (action.type.indexOf(']_' + START) > 0)) {
                handler = FETCH_ACTIONS_HANDLER[START];
            }
            else if (action.type[0] == '[' && (action.type.indexOf(']_' + REJECTED) > 0)) {
                handler = FETCH_ACTIONS_HANDLER[REJECTED];
            }
            else if (action.type[0] == '[' && (action.type.indexOf(']_' + FULFILLED) > 0)) {
                handler = FETCH_ACTIONS_HANDLER[FULFILLED];
            }
            else if (action.type[0] == '[' && (action.type.indexOf(']_' + SUCCESS) > 0)) {
                handler = FETCH_ACTIONS_HANDLER[SUCCESS];
            }
        }
    }
    // else if (action.type == Logout) {
    //     return {};
    // }
    return handler ? handler(state, action) : state;
}

export const fetchSelector = (state: any): any => state.fetch;

export function getFetchInfo(action: string, state: any): FetchInfo | FetchPagingInfo {
    const fetchData = fetchSelector(state);
    return fetchData[action] ? fetchData[action] : null;
}